import CryptoJS from "crypto-js";
import SHA256 from "crypto-js/sha256";
import AES from "crypto-js/aes";

const StoreTypeSesion = "session",
  StoreTypeLocation = "local";

/**
 * 基于与 `crypto-js` 的 AES 加密存储系统
 */
class Store {
  _storeType: string | null | undefined
  _defaultNs: string | null | undefined
  _holdKey: string | null | undefined
  /**
     * 实例化存储器
     * @param {string|null} [StoreType]
     * @param {string|null} [holdKey]
     * @param {string|null} [ns]
     */
  constructor(holdKey?: string | undefined | null, StoreType?: string | undefined | null, ns?: string) {
    this._storeType = StoreTypeLocation === StoreType
      ? StoreTypeLocation
      : StoreTypeSesion;
    this._defaultNs = ns
      ? SHA256(ns).toString()
      : "__" + SHA256(holdKey).toString();
    this._holdKey = holdKey;
  }
  /**
     * 获取的子存储器
     * @param {*} ns
     */
  getNs(ns: string) {
    return new Store(this._holdKey, this._storeType, ns);
  }

  /**
     * 设置获取存储的值
     * @param {string} key
     * @param {*} value
     * @param {string|null} [namespace]
     */
  val(key: string, value: any, namespace?: string | null) {
    namespace = (namespace || this._defaultNs) as string;
    if ("undefined" === typeof value) {
      return this.getValue(key, null, namespace);
    } else {
      return this.setValue(key, value, namespace);
    }
  }

  /**
     * 获取存储的值
     * @param {string} key
     * @param {*} [def]
     * @param {string} [namespace]
     */
  getValue(key: string, def?: any, namespace?: string) {
    namespace = (namespace || this._defaultNs) as string;
    let data = this._getObject(namespace);
    if (data) {
      return data[key] || def;
    }
    return def;
  }
  /**
     * 获取命名空间存储的全部值
     */
  get data() {
    return this._getObject(this._defaultNs as string);
  }

  /**
     * 修改存储值
     * @param {string} key
     * @param {*} value
     * @param {string} [namespace]
     */
  setValue(key: string, value: any, namespace?: string | null) {
    namespace = (namespace || this._defaultNs) as string;
    let data = this._getObject(namespace) || {};
    data[key] = value;
    let str = JSON.stringify(data);
    if (str) {
      str = AES.encrypt(str, this._holdKey).toString();
      this._setRaw(namespace, str);
      return true;
    }
    return false;
  }

  /**
     * 删除键值
     * @param {string} [key] 不设置全部删除
     * @param {string} [namespace]
     * @return {boolean}
     */
  delValue(key?: string, namespace?: string | null) {
    namespace = (namespace || this._defaultNs) as string;
    if (!key && namespace) {
      this._rmRaw(namespace);
      return true;
    }
    if (!key){
      return false;
    }
    let data = this._getObject(namespace) || {};
    if ("undefined" !== typeof data[key]) {
      return false;
    }
    delete data[key];
    let str = JSON.stringify(data);
    if (str) {
      str = AES.encrypt(str, this._holdKey).toString();
      this._setRaw(namespace, str);
      return true;
    }
    return false;
  }

  /**
     * 获取原始值
     * @param {*} key
     */
  _getRaw(key: string) {
    switch (this._storeType) {
      case StoreTypeLocation:
        return localStorage.getItem(key);
      default:
        return sessionStorage.getItem(key);
    }
  }

  /**
     * 设置原始值
     * @param {string} key
     * @param {*} valueStr
     */
  _setRaw(key: string, valueStr: string) {
    switch (this._storeType) {
      case StoreTypeLocation:
        return localStorage.setItem(key, valueStr);
      default:
        return sessionStorage.setItem(key, valueStr);
    }
  }

  /**
     * 删除键值
     * @param key
     * @private
     */
  _rmRaw(key: string) {
    if (!key) return;
    switch (this._storeType) {
      case StoreTypeLocation:
        return localStorage.removeItem(key);
      default:
        return sessionStorage.removeItem(key);
    }
  }

  /**
     * 获取命名空间对象
     * @param {*} key
     */
  _getObject(key: string) {
    let rawStr = this._getRaw(key);
    if (rawStr) {
      let oTxt = AES.decrypt(rawStr, this._holdKey).toString(CryptoJS.enc.Utf8);
      if (oTxt) {
        let data = null;
        try {
          data = JSON.parse(oTxt);
        } catch (er) {
          data = null;
        }
        return data;
      }
    }
    return null;
  }
}

export { Store, StoreTypeLocation, StoreTypeSesion };
